package maxinshiSomeipTool20.logMgr;

/**
 * 统一日志工具类 (UnifiedLogger)
 * 
 * 功能概述：
 * 提供单一接口实现多方式日志输出，通过内部开关灵活切换输出行为，无需修改业务代码。
 * 支持控制台输出、调用位置信息显示、日志文件记录三种核心功能。
 * 
 * 使用方法：
 * 1. 业务代码中直接调用：UnifiedLogger.log("日志内容");
 * 2. 通过修改本类中的开关常量控制输出方式
 * 3. 无需关注具体实现细节，专注于业务逻辑
 * 
 * 开关配置说明：
 * - CONSOLE_ENABLE: 控制是否输出到控制台（true=开启，false=关闭）
 * - CALLER_INFO_ENABLE: 控制是否显示调用位置信息（类名.方法名:行号）
 * - FILE_ENABLE: 控制是否写入日志文件（true=开启，false=关闭）
 * 
 * 配置示例场景：
 * 1. 开发调试：
 *    CONSOLE_ENABLE = true
 *    CALLER_INFO_ENABLE = true
 *    FILE_ENABLE = false
 * 
 * 2. 生产环境：
 *    CONSOLE_ENABLE = false
 *    CALLER_INFO_ENABLE = false
 *    FILE_ENABLE = true
 * 
 * 3. 问题排查：
 *    CONSOLE_ENABLE = true
 *    CALLER_INFO_ENABLE = true
 *    FILE_ENABLE = true
 * 
 * 日志文件管理：
 * - 自动创建日志文件，默认路径为"app_logs.log"
 * - 单个文件最大10MB，超过自动切割为备份文件
 * - 最多保留3个(MAX_BACKUP_COUNT的值)备份文件，自动删除最旧的备份
 */
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;

public class LogMgr {
    // ========================= 核心开关配置（修改这里切换输出方式）=========================
    /** 控制台输出开关：true=输出到控制台，false=不输出到控制台 */
    private static final boolean CONSOLE_ENABLE = true;
    /** 调用信息开关：true=输出[类名.方法名:行号]，false=不输出 */
    private static final boolean CALLER_INFO_ENABLE = true;
    /** 文件输出开关：true=输出到日志文件，false=不输出到文件 */
    private static final boolean FILE_ENABLE = false;

    // ========================= 日志文件配置（按需调整）=========================
    /** 日志文件路径（相对路径/绝对路径均可） */
    private static final String LOG_FILE_PATH = "app_logs.log";
    /** 单个日志文件最大大小（10MB，超过自动切割） */
    private static final long MAX_LOG_SIZE = 10 * 1024 * 1024;
    /** 最大备份日志文件数（超过自动删除最旧备份） */
    private static final int MAX_BACKUP_COUNT = 3;

    // ========================= 内部固定配置（无需修改）=========================
    /** 日志时间戳格式（精确到毫秒） */
    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    /** 线程安全锁（避免多线程写入文件冲突） */
    private static final ReentrantLock LOG_LOCK = new ReentrantLock();


    // ========================= 对外唯一日志接口（业务代码仅需调用这一个方法）=========================
    /**
     * 统一日志输出方法
     * @param message 日志内容（业务代码传入需要记录的信息）
     */
    public static void log(String message) {
        // 1. 构建完整日志内容（根据开关决定是否包含调用信息、时间戳）
        String fullLog = buildFullLog(message);

        // 2. 按开关输出到控制台
        outputToConsole(fullLog);

        // 3. 按开关输出到文件
        if (FILE_ENABLE) {
            outputToFile(fullLog);
        }
    }


    // ========================= 内部辅助方法（无需对外暴露）=========================
    /**
     * 构建完整日志内容（根据开关动态组装）
     */
    private static String buildFullLog(String message) {
        StringBuilder logBuilder = new StringBuilder();

        // 第一步：添加时间戳（固定包含，便于日志追溯）
        logBuilder.append("[").append(TIME_FORMAT.format(new Date())).append("] ");

        // 第二步：添加调用信息（根据CALLER_INFO_ENABLE开关决定）
        if (CALLER_INFO_ENABLE) {
            logBuilder.append(getCallerInfo()).append(" ");
        }

        // 第三步：添加核心日志内容
        logBuilder.append(message);

        return logBuilder.toString();
    }

    /**
     * 获取调用者信息（类名.方法名:行号）
     */
    private static String getCallerInfo() {
        try {
            // 栈深度=3：0=getStackTrace()，1=getCallerInfo()，2=buildFullLog()，3=业务代码调用处
            StackTraceElement caller = Thread.currentThread().getStackTrace()[3];
            String fullClassName = caller.getClassName();
            // 截取简单类名（去掉包名）
            String simpleClassName = fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
            // 格式化调用信息：[类名.方法名:行号]
            return String.format("[%s.%s:%d]", simpleClassName, caller.getMethodName(), caller.getLineNumber());
        } catch (Exception e) {
            // 异常时返回默认信息（避免日志工具自身抛错）
            return "[UnknownCaller]";
        }
    }

    /**
     * 输出日志到控制台（根据CONSOLE_ENABLE开关决定）
     */
    private static void outputToConsole(String fullLog) {
        if (CONSOLE_ENABLE) {
            System.out.println(fullLog);
        }
    }

    /**
     * 输出日志到文件（自动处理文件切割、备份）
     */
    private static void outputToFile(String fullLog) {
        // 加锁确保多线程安全（避免多个线程同时写文件导致内容错乱）
        LOG_LOCK.lock();
        try {
            // 检查日志文件大小：超过限制则切割（备份旧文件，创建新文件）
            checkAndSplitLogFile();

            // 追加写入日志（使用try-with-resources自动关闭流，避免资源泄漏）
            try (PrintWriter writer = new PrintWriter(new FileWriter(LOG_FILE_PATH, true))) {
                writer.println(fullLog);
            } catch (IOException e) {
                // 日志写入失败时，仅在控制台提示（避免影响业务流程）
                if (CONSOLE_ENABLE) {
                    System.err.println("[日志错误] 写入文件失败：" + e.getMessage());
                }
            }
        } finally {
            // 无论是否异常，都释放锁
            LOG_LOCK.unlock();
        }
    }

    /**
     * 检查日志文件大小，超过限制则自动切割（备份+新建）
     */
    private static void checkAndSplitLogFile() {
        File logFile = new File(LOG_FILE_PATH);
        // 若文件不存在/未超过大小，无需处理
        if (!logFile.exists() || logFile.length() <= MAX_LOG_SIZE) {
            return;
        }

        // 1. 删除最旧的备份文件（例如：备份3个时，删除app_logs.log.3）
        File oldestBackup = new File(LOG_FILE_PATH + "." + MAX_BACKUP_COUNT);
        if (oldestBackup.exists()) {
            oldestBackup.delete();
        }

        // 2. 旧备份文件序号递增（例如：app_logs.log.2 → app_logs.log.3，app_logs.log.1 → app_logs.log.2）
        for (int i = MAX_BACKUP_COUNT - 1; i >= 1; i--) {
            File oldBackup = new File(LOG_FILE_PATH + "." + i);
            if (oldBackup.exists()) {
                oldBackup.renameTo(new File(LOG_FILE_PATH + "." + (i + 1)));
            }
        }

        // 3. 当前日志文件重命名为备份1（app_logs.log → app_logs.log.1）
        logFile.renameTo(new File(LOG_FILE_PATH + ".1"));
    }
}
    